package com.qijian.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qijian.common.core.domain.entity.SysUser;
import com.qijian.common.exception.ServiceException;
import com.qijian.common.utils.DateUtils;
import com.qijian.common.utils.EmailUtil;
import com.qijian.common.utils.poi.ExcelUtil;
import com.qijian.system.domain.ExpireConfig;
import com.qijian.system.domain.ExpireConfigUsers;
import com.qijian.system.domain.ExpireData;
import com.qijian.system.domain.vo.ExpireConfigVo;
import com.qijian.system.mapper.ExpireConfigMapper;
import com.qijian.system.mapper.ExpireConfigUsersMapper;
import com.qijian.system.mapper.ExpireDataMapper;
import com.qijian.system.service.IExpireConfigService;
import com.qijian.system.service.IExpireConfigUsersService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 超期配置类Service业务层处理
 *
 * @author qijian
 * @date 2023-10-18
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ExpireConfigServiceImpl extends ServiceImpl<ExpireConfigMapper, ExpireConfig> implements IExpireConfigService {

    private final ExpireDataMapper expireDataMapper;

    private final ExpireConfigUsersMapper expireConfigUsersMapper;

    private final IExpireConfigUsersService expireConfigUsersService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrUpdateConfig(ExpireConfigVo configVo) {
        if(CollUtil.isEmpty(configVo.getUsers())){
            throw new ServiceException("E01:邮件发送人不能为空");
        }
        if(checkMailTypeExists(configVo.getEmailType(), configVo.getId())){
            throw new ServiceException("E01:邮件类型已存在");
        }
        this.saveOrUpdate(configVo);
        //更新发送人
        expireConfigUsersMapper.delete(new LambdaQueryWrapper<ExpireConfigUsers>().eq(ExpireConfigUsers::getMainId,configVo.getId()));
        List<ExpireConfigUsers> collect = configVo.getUsers().stream().map(u -> {
            ExpireConfigUsers expireConfigUsers = new ExpireConfigUsers();
            expireConfigUsers.setId(null);
            expireConfigUsers.setUserId(u.getUserId());
            expireConfigUsers.setEmail(u.getEmail());
            expireConfigUsers.setMainId(configVo.getId());
            expireConfigUsers.setNickName(u.getNickName());
            return expireConfigUsers;
        }).collect(Collectors.toList());
        expireConfigUsersService.saveBatch(collect);
        return true;

    }

    @Override
    public ExpireConfigVo getInfo(Long mainId) {
        ExpireConfig config = this.getById(mainId);
        if(config == null){
            return new ExpireConfigVo();
        }
        ExpireConfigVo vo = new ExpireConfigVo();
        BeanUtil.copyProperties(config,vo);
        vo.setUsers(expireConfigUsersMapper.getExpireConfigUsersByMainId(mainId));
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeExpireConfig(List<Long> ids) {
        if(CollUtil.isEmpty(ids)){
            return false;
        }
        this.removeByIds(ids);
        expireConfigUsersService.remove(new LambdaQueryWrapper<ExpireConfigUsers>().in(ExpireConfigUsers::getMainId,ids));
        return true;
    }

    private boolean checkMailTypeExists(String emailType,Long id){
        Long count = lambdaQuery().eq(ExpireConfig::getEmailType, emailType).ne(id != null, ExpireConfig::getId, id).count();
        if(count>0){
            return true;
        }
        return false;
    }

    /***
     *
     * @param emailType 邮件类型：也就是EmailUtil 中配置的key
     * @param sourceDataList
     * @param clazz
     * @param <T>
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public <T> void sendExpireMail(String emailType, List<T> sourceDataList, Class<T> clazz) {
        //根据邮件类型找到配置
        ExpireConfig expireConfig = getConfigByEmailType(emailType);
        if(expireConfig == null){
            log.warn("根据邮件类型[{}]未找到邮件升级超期配置,不做升级提醒处理",emailType);
            return;
        }
        if(!expireConfig.getIsEnabled()){
            log.info("邮件类型[{}]未开启超期配置,不做升级提醒处理",emailType);
        }

        List<String> emails = getUserEmails(expireConfig.getId());
        if(CollUtil.isEmpty(emails)){
            log.info("邮件类型[{}]没有配置有效的邮件用户，不做升级提醒处理",emailType);
            return;
        }

        List<Long> sourceDataIds = getSourceDataIds(sourceDataList, expireConfig.getSourceKey());
        if(CollUtil.isEmpty(sourceDataIds)){
            log.warn("获取到的原始id列表为空,不做升级提醒处理");
            return;
        }

        //如果本次sourceDataIds包含已经置为无效的数据，需要从头开始进行预警升级，所以需要重置下次预警时间
        this.resetInvalidSourceExpireDate(emailType,sourceDataIds,expireConfig.getExpireDays());

        //保存原始数据，计算超期时间,状态为有效
        this.saveValidSourceData(sourceDataIds,emailType,expireConfig.getExpireDays());

        List<Long> expireSourceIds = getExpireValidSourceList(emailType);
        if(CollUtil.isEmpty(expireSourceIds)){
            log.info("根据邮件类型[{}]没有需要升级提醒的数据,不做升级提醒处理",emailType);
            return;
        }

        //预警表中的有效数据与当前查询出的预警数据做并集出来的数据，就是需要发送预警的数据
        Collection<Long> needExpireSourceIds = CollectionUtil.intersection(expireSourceIds, sourceDataIds);
        if(CollUtil.isEmpty(needExpireSourceIds)){
            log.info("根据邮件类型[{}]获取到的预警数据与源数据没有并集数据,不做升级提醒处理",emailType);
            return;
        }
        //根据id过滤出原始数据
        List<T> fileSourceDataList = getFileSourceDataList(sourceDataList, needExpireSourceIds, expireConfig.getSourceKey());
        File projectFile = FileUtil.createTempFile(expireConfig.getTitle() + IdWorker.getId(), ".xlsx", FileUtil.getTmpDir(), true);
//        new ExcelUtil<>(clazz).exportExcel(projectFile, fileSourceDataList, null);
        EmailUtil.sendEmailDetailAndFiles(expireConfig.getEmailType(),emails,null, expireConfig.getTitle(), null, null, projectFile);
        FileUtil.del(projectFile);

        //当前预警表中存在的,而查出来的预警数据不存在的,再把数据置为无效，如果下次再出现这种数据，则过期时间不会变，状态是有效的，这种数据还会继续发送
        Collection<Long> invalidSourceIds = CollectionUtil.disjunction(expireSourceIds, sourceDataIds);
        invalidSourceData(invalidSourceIds,emailType);
    }

    private List<String> getUserEmails(Long mainId){
        List<SysUser> users = expireConfigUsersMapper.getExpireConfigUsersByMainId(mainId);
        if(CollUtil.isEmpty(users)){
            return Collections.emptyList();
        }
        return users.stream().map(SysUser::getEmail).filter(EmailUtil::isEmail).collect(Collectors.toList());

    }

    /***
     * 测试方法
     * @param emailType
     * @param sourceDataList
     */
    @Override
    public void testSendExpireMail(String emailType, List<Long> sourceDataList) {

        //先找到之前的无效数据，把之前的无效数据时间重置
        this.resetInvalidSourceExpireDate(emailType,sourceDataList,3L);
        this.saveValidSourceData(sourceDataList,emailType,3L);
        List<Long> expireValidSourceList = getExpireValidSourceList(emailType);
        if(CollUtil.isEmpty(expireValidSourceList)){
            return;
        }
        Collection<Long> needExpireSourceIds = CollectionUtil.intersection(expireValidSourceList, sourceDataList);
        log.info("需要发送邮件的id列表:{}",needExpireSourceIds);
        Collection<Long> invalidSourceIds = CollectionUtil.disjunction(expireValidSourceList, sourceDataList);
        invalidSourceData(invalidSourceIds,emailType);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateStatus(ExpireConfigVo expireConfigVo) {
        if(expireConfigVo.getId() == null || expireConfigVo.getId()<0L){
            throw new ServiceException("E01:id不存在");
        }
        if(expireConfigVo.getIsEnabled() == null){
            throw new ServiceException("E01:状态值不正确");
        }
        ExpireConfig config = new ExpireConfig();
        config.setId(expireConfigVo.getId());
        config.setIsEnabled(expireConfigVo.getIsEnabled());
        return baseMapper.updateById(config);
    }

    private void resetInvalidSourceExpireDate(String emailType, List<Long> sourceDataList,long expireDays) {
        expireDataMapper.resetInvalidSourceExpireDate(emailType,sourceDataList,expireDays);
    }

    private <T>  List<T> getFileSourceDataList(List<T> sourceDataList, Collection<Long> needExpireSourceIds,String sourceKey) {
        List<T> resultList = new ArrayList<>();
        for (T element : sourceDataList) {
            Long fieldValue = getFieldValue(element, sourceKey);
            if(fieldValue !=null && needExpireSourceIds.contains(fieldValue)){
                resultList.add(element);
            }
        }
        return resultList;
    }

    /***
     * 保存需要下次提醒的数据: 这个是全量数据，所以存在重复的情况，
     * 复合主键，重复的忽略只更新updateTime和is_valid 的状态
     * 因为下次如果这个sourceId的数据再出现，说明这个数据仍然需要做超期提醒
     * @param list
     */
    private void saveValidSourceData(List<Long> list,String emailType,Long expireDays){
        List<ExpireData> dataList = new ArrayList<>();
        for (Long sourceId : list) {
            ExpireData data = new ExpireData();
            data.setSourceId(sourceId);
            data.setIsValid(true);
            data.setCreateBy("系统");
            data.setUpdateBy("系统");
            data.setExpireDate(DateUtils.addDays(new Date(),expireDays.intValue())); // 过期时间
            data.setEmailType(emailType);
            dataList.add(data);
        }
        expireDataMapper.batchInsertUpdate(dataList);
    }

    /***
     * 置为无效数据
     * @param list
     */
    private void invalidSourceData(Collection<Long> list,String emailType){
        if(CollUtil.isNotEmpty(list)){
            expireDataMapper.batchInvalid(list,emailType);
        }
    }
    /***
     * 获取属性sourceKey的方法,默认sourceKey 为long类型
     * @return 转换失败返回null
     */
    private Long getFieldValue(Object element,String sourceKey){
        Class<?> elementClass = element.getClass();
        try {
            Field field = elementClass.getDeclaredField(sourceKey);
            field.setAccessible(true);
            Object value = field.get(element);
            if(value instanceof Long){
                return (Long) value;
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            //e.printStackTrace(); ignore error
            log.warn("获取元素{}属性{}错误:{}",JSONUtil.toJsonStr(element),sourceKey,e.getMessage());
        }
        return null;
    }

    /***
     * 获取sourceId 列表
     * @param sourceDataList
     * @param sourceKey
     * @return
     */
    private List<Long> getSourceDataIds(List<?> sourceDataList,String sourceKey){
        if(CollUtil.isEmpty(sourceDataList) || StrUtil.isBlank(sourceKey)){
            return Collections.emptyList();
        }
        List<Long> sourceIds = new ArrayList<>();
        for (Object element : sourceDataList) {
            Long fieldValue = this.getFieldValue(element,sourceKey);
            if(fieldValue!=null){
                sourceIds.add(fieldValue);
            }
        }
        return sourceIds;
    }

    /***
     * 获取当天及之前需要预警的id
     * @param emailType
     * @return
     */
    private List<Long> getExpireValidSourceList(String emailType){
        List<ExpireData> list = expireDataMapper.selectList(new LambdaQueryWrapper<ExpireData>()
                .eq(ExpireData::getEmailType, emailType)
                .eq(ExpireData::getIsValid,true)
                .le(ExpireData::getExpireDate,new Date()));
        if(CollUtil.isEmpty(list)){
            return Collections.emptyList();
        }
        return list.stream().map(ExpireData::getSourceId).collect(Collectors.toList());
    }
    private ExpireConfig getConfigByEmailType(String emailType){
        return this.getOne(new LambdaQueryWrapper<ExpireConfig>().eq(ExpireConfig::getEmailType,emailType));
    }
}
